#include "number.h"

/*
 *  Frees the memory block pointed to by interval.
 */
void interval_free (Interval* interval)
{
    if (interval)
    {
        if (interval->n)
        {
            real_free(interval->n);
        }
        
        if (interval->l)
        {
            real_free(interval->l);
        }
        
        if (interval->u)
        {
            real_free(interval->u);
        }
        
        free(interval);
        interval = NULL;
    }
}

/*
 *  Returns the size of the string representation of interval as a long.
 */
long interval_strlen (Interval* interval)
{
    if (interval_degenerate_p(interval))
    {
        return real_strlen(interval->n);
    }
    else
    {
        int cmp = 1;
        int space = 1;
        int bracket = 1;
        int l = real_strlen(interval->l);
        int n = real_strlen(interval->n);
        int u = real_strlen(interval->u);
        
        return bracket + l + space + cmp + space + bracket + n + bracket + space + cmp + space + u + bracket;
    }
}

/*
 *  Puts a string representation of interval into the string pointed to by buf.
 *  
 *    interval: [1:1 < (2:1) < 3:1] => str: "[1:1 < (2:1) < 3:1]"
 *    interval: [0:0 ≤ (0:0) ≤ 0:0] => str: "0:0"
 */
void interval_print(char* buf, Interval* interval)
{
    if (interval_degenerate_p(interval))
    {
        real_print(buf, interval->n);
    }
    else
    {
        char* L = (interval->L) ? "≤" : "<";
        char* R = (interval->R) ? "≤" : "<";
        
        char l[real_strlen(interval->l)];
        char n[real_strlen(interval->n)];
        char u[real_strlen(interval->u)];
        
        real_print(l, interval->l);
        real_print(n, interval->n);
        real_print(u, interval->u);
        
        sprintf(buf, "[%s %s (%s) %s %s]", l, L, n, R, u);
    }
}

/*
 *  Returns 1 if interval contains real, 0 otherwise.
 */
int interval_span_p (Interval* interval, Real* real)
{
    return !(real_gt_p(interval->l, real) || real_lt_p(interval->u, real) || (real_eq_p(interval->l, real) && !interval->L) || (real_eq_p(interval->u, real) && !interval->R));
}

/*
 *  Returns 1 if interval contains zero, 0 otherwise.
 */
int interval_span_zero_p (Interval* interval)
{
    return (interval->L && ZERO(interval->l)) || (interval->R && ZERO(interval->u)) || (NEG(interval->l) && POS(interval->u));
}

/*
 *  Returns 1 if interval contains only zero, 0 otherwise.
 */
int interval_zero_p (Interval* interval)
{
    return ZERO(interval->l) && ZERO(interval->u);
}

/*
 *  Returns 1 if interval contains exactly one real number, 0 otherwise.
 */
int interval_degenerate_p (Interval* interval)
{
    return real_eq_p(interval->l, interval->u);
}

/*
 *  Returns a pointer to a newly allocated Interval with the given value. The
 *  given reals will be referenced by the new interval and must be freed only
 *  when this interval is freed.
 *  
 *    l: (1:1), L: 0, n: (2:1), R: 1, u: (3:1) => interval: {1:1 < 2:1 ≤ 3:1}
 */
Interval* interval_new (Real* l, int L, Real* n, int R, Real* u)
{
    Interval* interval = malloc(sizeof(Interval));
    
    if (interval == NULL)
    {
        return NULL;
    }
    
    memset(interval, 0, sizeof(Interval));
    
    if (NaN(l) || NaN(n) || NaN(u))
    {
        interval->L = true;
        interval->R = true;
        
        interval->l = real_neg_inf();
        interval->n = real_nan();
        interval->u = real_pos_inf();
        
        real_free(n);
        real_free(l);
        real_free(u);
    }
    else
    {
        interval->L = (real_eq_p(l, u) || real_eq_p(n, l) || L);
        interval->R = (real_eq_p(l, u) || real_eq_p(n, u) || R);
        
        interval->l = l;
        interval->n = n;
        interval->u = u;
    }
    
    return interval;
}

/*
 *  Returns a pointer to a newly allocated Interval containing only the given
 *  real.
 */
Interval* interval_from_real (Real* real)
{
    Real* l = real_dup(real);
    Real* u = real_dup(real);
    
    return interval_new(l, true, real, true, u);
}

/*
 *  Returns a pointer to a newly allocated Interval containing a NaN value.
 */
Interval* interval_nan ()
{
    Real* l = real_neg_inf();
    Real* n = real_nan();
    Real* u = real_pos_inf();
    
    return interval_new(l, true, n, true, u);
}

/*
 *  Returns a pointer to a newly allocated Interval containing only +∞.
 */
Interval* interval_pos_inf ()
{
    return interval_from_real(real_pos_inf());
}

/*
 *  Returns a pointer to a newly allocated Interval containing only -∞.
 */
Interval* interval_neg_inf ()
{
    return interval_from_real(real_neg_inf());
}

/*
 *  Returns a pointer to a newly allocated Interval containing only 0.
 */
Interval* interval_zero ()
{
    return interval_from_real(real_zero());
}

/*
 *  Returns a pointer to a newly allocated Interval containing only 1.
 */
Interval* interval_one ()
{
    return interval_from_real(real_one());
}

/*
 *  Returns a pointer to a newly allocated Interval containing π, accurate to
 *  digs digits.
 */
Interval* interval_pi ()
{
    Real* l = real_pi(ROUND_DOWN);
    Real* n = real_pi(ROUND_NEAREST);
    Real* u = real_pi(ROUND_UP1);
    
    return interval_new(l, false, n, false, u);
}

/*
 *  Returns a pointer to a newly allocated Interval containing e, accurate to
 *  digs digits.
 */
Interval* interval_e ()
{
    Real* l = real_e(ROUND_DOWN);
    Real* n = real_e(ROUND_NEAREST);
    Real* u = real_e(ROUND_UP1);
    
    return interval_new(l, false, n, false, u);
}

/*
 *  Returns a pointer to a newly allocated Interval with the same value as the
 *  given interval.
 */
Interval* interval_dup (Interval* interval)
{
    Real* l = real_dup(interval->l);
    Real* n = real_dup(interval->n);
    Real* u = real_dup(interval->u);
    
    return interval_new(l, interval->L, n, interval->R, u);
}

/*
 *  Returns a pointer to a newly allocated Interval containing a + b.
 */
Interval* interval_add (Interval* a, Interval* b)
{
    int L = (a->L && b->L);
    int R = (a->R && b->R);
    
    Real* l = real_add(a->l, b->l);
    Real* n = real_add(a->n, b->n);
    Real* u = real_add(a->u, b->u);
    
    return interval_new(l, L, n, R, u);
}

/*
 *  Returns a pointer to a newly allocated Interval containing a - b.
 */
Interval* interval_subtract (Interval* a, Interval* b)
{
    int L = (a->L && b->R);
    int R = (a->R && b->L);
    
    Real* l = real_subtract(a->l, b->u);
    Real* n = real_subtract(a->n, b->n);
    Real* u = real_subtract(a->u, b->l);
    
    return interval_new(l, L, n, R, u);
}

/*
 *  Returns a pointer to a newly allocated Interval containing a * b.
 */
Interval* interval_multiply (Interval* a, Interval* b)
{
    RealBounds* bounds = real_bounds_new();
    
    real_bounds_update(bounds, real_multiply(a->l, b->l), (a->L && b->L));
    real_bounds_update(bounds, real_multiply(a->l, b->u), (a->L && b->R));
    real_bounds_update(bounds, real_multiply(a->u, b->l), (a->R && b->L));
    real_bounds_update(bounds, real_multiply(a->u, b->u), (a->R && b->R));
    
    int L = (bounds->min_closed);
    int R = (bounds->max_closed);
    
    Real* l = real_dup(bounds->min);
    Real* n = real_multiply(a->n, b->n);
    Real* u = real_dup(bounds->max);
    
    real_bounds_free(bounds);
    
    return interval_new(l, L, n, R, u);
}

/*
 *  Returns a pointer to a newly allocated Interval containing a / b.
 */
Interval* interval_divide (Interval* a, Interval* b)
{
    RealBounds* bounds = real_bounds_new();
    
    real_bounds_update(bounds, real_divide(a->l, b->l, ROUND_DOWN), (a->L && b->L));
    real_bounds_update(bounds, real_divide(a->l, b->u, ROUND_DOWN), (a->L && b->R));
    real_bounds_update(bounds, real_divide(a->u, b->l, ROUND_DOWN), (a->R && b->L));
    real_bounds_update(bounds, real_divide(a->u, b->u, ROUND_DOWN), (a->R && b->R));
    real_bounds_update(bounds, real_divide(a->l, b->l, ROUND_UP), (a->L && b->L));
    real_bounds_update(bounds, real_divide(a->l, b->u, ROUND_UP), (a->L && b->R));
    real_bounds_update(bounds, real_divide(a->u, b->l, ROUND_UP), (a->R && b->L));
    real_bounds_update(bounds, real_divide(a->u, b->u, ROUND_UP), (a->R && b->R));
    
    int L = (bounds->min_closed);
    int R = (bounds->max_closed);
    
    Real* l = real_dup(bounds->min);
    Real* n = real_divide(a->n, b->n, ROUND_NEAREST);
    Real* u = real_dup(bounds->max);
    
    real_bounds_free(bounds);
    
    return interval_new(l, L, n, R, u);
}

/*
 *  Returns a pointer to a newly allocated Interval containing -interval.
 */
Interval* interval_negate (Interval* interval)
{
    Real* l = real_negate(interval->u);
    Real* n = real_negate(interval->n);
    Real* u = real_negate(interval->l);
    
    return interval_new(l, interval->R, n, interval->L, u);
}

/*
 *  Returns a pointer to a newly allocated Interval containing the elements of
 *  interval rounded to the nearest integer values.
 */
Interval* interval_round (Interval* interval)
{
    Real* l = real_round(interval->l);
    Real* n = real_round(interval->n);
    Real* u = real_round(interval->u);
    
    return interval_new(l, true, n, true, u);
}

/*
 *  Returns a pointer to a newly allocated Interval containing the elements of
 *  interval rounded to the next closest integer values in the direction of
 *  positive infinity.
 */
Interval* interval_round_ceiling (Interval* interval)
{
    Real* l = real_ceil(interval->l);
    Real* n = real_ceil(interval->n);
    Real* u = real_ceil(interval->u);
    
    return interval_new(l, true, n, true, u);
}

/*
 *  Returns a pointer to a newly allocated Interval containing the elements of
 *  interval rounded to the next closest integer values in the direction of
 *  negative infinity.
 */
Interval* interval_round_floor (Interval* interval)
{
    Real* l = real_floor(interval->l);
    Real* n = real_floor(interval->n);
    Real* u = real_floor(interval->u);
    
    return interval_new(l, true, n, true, u);
}

/*
 *  Returns a pointer to a newly allocated Interval containing the elements of
 *  interval rounded to the next closest integer values in the direction away
 *  from the origin.
 */
Interval* interval_round_infinity (Interval* interval)
{
    Real* l = real_round_infinity(interval->l);
    Real* n = real_round_infinity(interval->n);
    Real* u = real_round_infinity(interval->u);
    
    return interval_new(l, true, n, true, u);
}

/*
 *  Returns a pointer to a newly allocated Interval containing the elements of
 *  interval rounded to the next closest integer values in the direction of the
 *  origin.
 */
Interval* interval_round_origin (Interval* interval)
{
    Real* l = real_round_origin(interval->l);
    Real* n = real_round_origin(interval->n);
    Real* u = real_round_origin(interval->u);
    
    return interval_new(l, true, n, true, u);
}
